package org.xtext.runtime.plan;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.xtext.runtime.formula.IFormula;
import org.xtext.runtime.interpreter.Agent;
import org.xtext.runtime.term.Variable;
import org.xtext.runtime.util.Bindings;
import org.xtext.runtime.util.EvaluateTermsVisitor;

public class WhileStep implements IPlanStep {
	private IFormula guard;
	private IPlanStep body;
	private Set<Variable> variables;
	private boolean first = true;
	
	public WhileStep(IFormula guard, IPlanStep plan) {
		this.guard = guard;
		this.body = plan;

		// generate the set of free variables for the associated plan body
		variables = body.variables();
	}
	
	@Override
	public WhileStep apply(Bindings set) {
		guard.apply(set);
		body.apply(set);
		return this;
	}

	public String toString() {
		return toString("");
	}
	
	public String toString(String tabs) {
		return tabs + "while (" + guard + ") ...";
	}

	public String toFullString(String tabs) {
		return tabs + "while (" + guard + ")" + body.toFullString(tabs + IPlanStep.TAB);
	}

	public IFormula guard() {
		return guard;
	}
	
	public IPlanStep plan() {
		return body;
	}
	
	Set<Variable> workingVariables;
	
	@Override
	public void handle(Agent agent, PlanStack planStack) {
//		System.out.println("variables: " + variables);
		if (first) {
			workingVariables = new HashSet<Variable>();
			for (Variable variable : variables) {
				if (variable.isFree()) {
//					System.out.println("removing variable: " + variable);
					workingVariables.add(variable);
				}
			}
			first = false;
		}

		// unbind any variables that were designated as free variables...
		Set<Variable> guardVars = guard.variables();

		// clear variables that appear in the guard (used for matching the state)
		for (Variable variable : workingVariables) {
			if (guardVars.contains(variable)) {
				variable.clear();
			}
		}

		List<Bindings> result = agent.query((IFormula) guard.accept(new EvaluateTermsVisitor(agent)));
		if (result != null) {
			// now clear variables not used in the guard - this means that
			// the last value will remain associated with these variables
			// when the loop finishes.
			for (Variable variable : workingVariables) {
				if (!guardVars.contains(variable)) {
					variable.clear();
				}
			}
			planStack.back();
			planStack.addPlanStep(body);
			planStack.getNextActivity().handle(agent, planStack);
		}
	}

	@Override
	public Set<Variable> variables() {
		Set<Variable> variables = new HashSet<Variable>();
		variables.addAll(guard.variables());
		variables.addAll(body.variables());
		return variables;
	}

	@Override
	public WhileStep copy(Map<String, Variable> vars) {
		return new WhileStep(guard.copy(vars), body.copy(vars));
	}

	@Override
	public void reset() {
		first = true;
	}
}
